親代物件負責架構,實作細節則交給繼承的子代物件負責。
試想一個情境,物件內某個方法的實作內容有各種可能,如果沒有思考,可能會依照實作內容的不同,建立多個物件。
class Operation1 {
  operation() {
    // 相同處,都做點什麼
    // 差異處 AAA
  }
}
class Operation2 {
  operation() {
    // 相同處,都做點什麼
    // 差異處 BBB
  }
}
隨著物件數量增加,大量重複的程式碼會不斷出現。此時,該思考的是,如何將重複的部分抽出,並且不影響原有功能。剛好,每個物件的差異都在實作的細節,所以可以建立一個親代物件,除了將共同的程式碼封裝之外,建立一個供子代實作但親代只負責開規格的方法。
class OperationPrototype {
  operation() {
    // 相同處,都做點什麼
  }
  operationDetails() { }
}
class Operation1 extends OperationPrototype {
  /** @override */
  operationDetails() { }() {
    // 差異處 AAA
  }
}
class Operation2 extends OperationPrototype {
  /** @override */
  operationDetails() { }() {
    // 差異處 BBB
  }
}
如此一來,子代只要負責實作的內容,整體的框架仍然由親代制定,子代沒有能力做更動。
作法是:
以下範例以「簡易打掃家庭」為核心製作。
建立親代虛擬層物件:Housekeeping
public abstract class Housekeeping {
    protected String type;
    Housekeeping(String type) {
        this.type = type;
    }
    public String washClothes() {
        return washDetails();
    }
    protected abstract String washDetails();
    public String cleanUp() {
        return cleanUpDetails();
    }
    protected abstract String cleanUpDetails();
}
建立子代物件:SimpleHousekeeping、AppliancesHousekeeping(Template 物件)
public class SimpleHousekeeping extends Housekeeping {
    public SimpleHousekeeping() {
        super("簡單的打掃方式");
    }
    @Override
    protected String washDetails() {
        System.out.println("手洗衣服");
        for (int i = 0; i < 10; i++) {
            System.out.println("手洗中" + ".".repeat(i));
        }
        return "一堆乾淨的衣服,花了兩小時";
    }
    @Override
    protected String cleanUpDetails() {
        System.out.println("用掃把打掃");
        for (int i = 0; i < 8; i++) {
            System.out.println("打掃中" + ".".repeat(i));
        }
        return "乾淨的房間,腰痠背痛";
    }
}
public class AppliancesHousekeeping extends Housekeeping {
    public AppliancesHousekeeping() {
        super("家電幫忙下的打掃方式");
    }
    @Override
    protected String washDetails() {
        System.out.println("洗衣機洗衣服");
        for (int i = 0; i < 5; i++) {
            System.out.println("嘟".repeat(i));
        }
        return "一堆乾淨的衣服,花了一小時";
    }
    @Override
    protected String cleanUpDetails() {
        System.out.println("用吸塵器打掃");
        for (int i = 0; i < 3; i++) {
            System.out.println("嗚".repeat(i));
        }
        return "乾淨的房間,輕鬆完成";
    }
}
測試,模擬使用不同器具打掃家庭:TicketMachineStrategySample
public class HousekeepingTemplateSample {
    public static void main(String[] args) {
        Housekeeping housekeeping = null;
        String washClothesResult = null;
        String cleanUpResult = null;
        System.out.println("沒什麼閒錢,整理家務簡單即可");
        housekeeping = new SimpleHousekeeping();
        washClothesResult = housekeeping.washClothes();
        cleanUpResult = housekeeping.cleanUp();
        System.out.println("清理結果: " + washClothesResult + ";" + cleanUpResult);
        System.out.println("\n有點錢了,買些家電幫忙整理家務");
        housekeeping = new AppliancesHousekeeping();
        washClothesResult = housekeeping.washClothes();
        cleanUpResult = housekeeping.cleanUp();
        System.out.println("\n清理結果: " + washClothesResult + ";" + cleanUpResult);
    }
}
建立親代虛擬層物件:Housekeeping
/** @abstract */
class Housekeeping {
  /** @param {string} type */
  constructor(type) {
    this.type = type;
  }
  washClothes() {
    return this.washDetails();
  }
  /** @abstract */
  washDetails() { return ""; }
  cleanUp() {
    return this.cleanUpDetails();
  }
  /** @abstract */
  cleanUpDetails() { return ""; }
}
建立子代物件:SimpleHousekeeping、AppliancesHousekeeping(Template 物件)
class SimpleHousekeeping extends Housekeeping {
  constructor() {
    super("簡單的打掃方式");
  }
  /** @override */
  washDetails() {
    console.log("手洗衣服");
    for (let i = 0; i < 10; i++) {
      console.log("手洗中" + ".".repeat(i));
    }
    return "一堆乾淨的衣服,花了兩小時";
  }
  /** @override */
  cleanUpDetails() {
    console.log("用掃把打掃");
    for (let i = 0; i < 8; i++) {
      console.log("打掃中" + ".".repeat(i));
    }
    return "乾淨的房間,腰痠背痛";
  }
}
class AppliancesHousekeeping extends Housekeeping {
  constructor() {
    super("家電幫忙下的打掃方式");
  }
  /** @override */
  washDetails() {
    console.log("洗衣機洗衣服");
    for (let i = 0; i < 5; i++) {
      console.log("嘟".repeat(i));
    }
    return "一堆乾淨的衣服,花了一小時";
  }
  /** @override */
  cleanUpDetails() {
    console.log("用吸塵器打掃");
    for (let i = 0; i < 3; i++) {
      console.log("嗚".repeat(i));
    }
    return "乾淨的房間,輕鬆完成";
  }
}
測試,模擬使用不同器具打掃家庭:TicketMachineStrategySample
const housekeepingTemplateSample = () => {
  let housekeeping = null;
  let washClothesResult = null;
  let cleanUpResult = null;
  console.log("沒什麼閒錢,整理家務簡單即可");
  housekeeping = new SimpleHousekeeping();
  washClothesResult = housekeeping.washClothes();
  cleanUpResult = housekeeping.cleanUp();
  console.log("清理結果: " + washClothesResult + ";" + cleanUpResult);
  console.log("\n有點錢了,買些家電幫忙整理家務");
  housekeeping = new AppliancesHousekeeping();
  washClothesResult = housekeeping.washClothes();
  cleanUpResult = housekeeping.cleanUp();
  console.log("\n清理結果: " + washClothesResult + ";" + cleanUpResult);
};
housekeepingTemplateSample();
Template Method 模式有趣在於,如果常常使用 OOP 原則 - 繼承(Inheritance)的話,那閱讀這個模式的內容時會恍然大悟,並且有感而發:「原來平常開發的習慣,即使再怎麼簡單,也是一種模式?!」
想想也是如此,沒有實際接觸、了解前,不用把不知道的事情想成是偉大、繁瑣、有難度的、不易暸解等等,徒然增加自身在學習上的障礙物。
明天將介紹 Behavioural patterns 的第十一個也是該類別最後一個模式:Visitor 模式。